#include "U2GLESFrameBufferObject.h"

#include "U2Root.h"
#include "U2GLESHardwarePixelBuffer.h"
#include "U2GLESFBORenderTexture.h"
#include "U2GLESDepthBuffer.h"


U2EG_NAMESPACE_USING


//-----------------------------------------------------------------------------
GLESFrameBufferObject::GLESFrameBufferObject(GLESFBOManager *manager, u2uint fsaa)
: mManager(manager)
, mNumSamples(fsaa)
{
    /// Generate framebuffer object
    glGenFramebuffersOES(1, &mFB);
    GL_CHECK_ERROR;

	// Check multisampling
#if GL_APPLE_framebuffer_multisample
		// Check samples supported
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFB);
        GL_CHECK_ERROR;
		GLint maxSamples;
		glGetIntegerv(GL_MAX_SAMPLES_APPLE, &maxSamples);
        GL_CHECK_ERROR;
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
        GL_CHECK_ERROR;
		mNumSamples = std::min(mNumSamples, (GLsizei)maxSamples);
#else
		mNumSamples = 0;
#endif
	// will we need a second FBO to do multisampling?
	if (mNumSamples)
	{
		glGenFramebuffersOES(1, &mMultisampleFB);
        GL_CHECK_ERROR;
	}
	else
	{
		mMultisampleFB = 0;
	}
    
    /// Initialise state
    mDepth.buffer=0;
    mStencil.buffer=0;
    for(size_t x = 0; x < U2_MAX_MULTIPLE_RENDER_TARGETS; ++x)
    {
        mColour[x].buffer=0;
    }
}

GLESFrameBufferObject::~GLESFrameBufferObject()
{
    mManager->releaseRenderBuffer(mDepth);
    mManager->releaseRenderBuffer(mStencil);
	mManager->releaseRenderBuffer(mMultisampleColourBuffer);
    /// Delete framebuffer object
    glDeleteFramebuffersOES(1, &mFB);
    GL_CHECK_ERROR;

	if (mMultisampleFB)
		glDeleteFramebuffersOES(1, &mMultisampleFB);

    GL_CHECK_ERROR;
}

void GLESFrameBufferObject::bindSurface(size_t attachment, const U2GLESSurfaceDesc &target)
{
    assert(attachment < U2_MAX_MULTIPLE_RENDER_TARGETS);
    mColour[attachment] = target;
	// Re-initialise
	if(mColour[0].buffer)
		initialise();
}

void GLESFrameBufferObject::unbindSurface(size_t attachment)
{
    assert(attachment < U2_MAX_MULTIPLE_RENDER_TARGETS);
    mColour[attachment].buffer = 0;
	// Re-initialise if buffer 0 still bound
	if(mColour[0].buffer)
		initialise();
}

void GLESFrameBufferObject::initialise()
{
	// Release depth and stencil, if they were bound
    mManager->releaseRenderBuffer(mDepth);
    mManager->releaseRenderBuffer(mStencil);
	mManager->releaseRenderBuffer(mMultisampleColourBuffer);
    /// First buffer must be bound
    if(!mColour[0].buffer)
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, 
            "Attachment 0 must have surface attached",
            "GLESFrameBufferObject::initialise");
    }

	// If we're doing multisampling, then we need another FBO which contains a
	// renderbuffer which is set up to multisample, and we'll blit it to the final 
	// FBO afterwards to perform the multisample resolve. In that case, the 
	// mMultisampleFB is bound during rendering and is the one with a depth/stencil

    /// Store basic stats
    size_t width = mColour[0].buffer->getWidth();
    size_t height = mColour[0].buffer->getHeight();
    GLuint format = mColour[0].buffer->getGLFormat();

	// Bind simple buffer to add colour attachments
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, mFB);
    GL_CHECK_ERROR;

    /// Bind all attachment points to frame buffer
    for(size_t x = 0; x < U2_MAX_MULTIPLE_RENDER_TARGETS; ++x)
    {
        if(mColour[x].buffer)
        {
            if(mColour[x].buffer->getWidth() != width || mColour[x].buffer->getHeight() != height)
            {
                U2StringStream ss;
                ss << "Attachment " << x << " has incompatible size ";
                ss << mColour[x].buffer->getWidth() << "x" << mColour[x].buffer->getHeight();
                ss << ". It must be of the same as the size of surface 0, ";
                ss << width << "x" << height;
                ss << ".";
                U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, ss.str(), "GLESFrameBufferObject::initialise");
            }
            if(mColour[x].buffer->getGLFormat() != format)
            {
                U2StringStream ss;
                ss << "Attachment " << x << " has incompatible format.";
                U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, ss.str(), "GLESFrameBufferObject::initialise");
            }
            mColour[x].buffer->bindToFramebuffer(GL_COLOR_ATTACHMENT0_OES+x, mColour[x].zoffset);
        }
        else
        {
            // Detach
            glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES+x,
                GL_RENDERBUFFER_OES, 0);
            GL_CHECK_ERROR;
        }
    }

	// Now deal with depth / stencil
	if (mMultisampleFB)
	{
		// Bind multisample buffer
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, mMultisampleFB);
        GL_CHECK_ERROR;

		// Create AA render buffer (colour)
		// note, this can be shared too because we blit it to the final FBO
		// right after the render is finished
		mMultisampleColourBuffer = mManager->requestRenderBuffer(format, width, height, mNumSamples);

		// Attach it, because we won't be attaching below and non-multisample has
		// actually been attached to other FBO
		mMultisampleColourBuffer.buffer->bindToFramebuffer(GL_COLOR_ATTACHMENT0_OES, 
			mMultisampleColourBuffer.zoffset);

		// depth & stencil will be dealt with below
	}

    /// Depth buffer is not handled here anymore.
	/// See GLESFrameBufferObject::attachDepthBuffer() & RenderSystem::setDepthBufferFor()

	/// Do glDrawBuffer calls
	GLenum bufs[U2_MAX_MULTIPLE_RENDER_TARGETS];
	for(size_t x = 0; x < U2_MAX_MULTIPLE_RENDER_TARGETS; ++x)
	{
		// Fill attached colour buffers
		if(mColour[x].buffer)
		{
			bufs[x] = GL_COLOR_ATTACHMENT0_OES + x;
		}
		else
		{
			bufs[x] = GL_NONE;
		}
	}

    /// Check status
    GLuint status;
    status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
    GL_CHECK_ERROR;

    /// Bind main buffer
#if U2_PLATFORM == U2_PLATFORM_APPLE_IOS
    // The screen buffer is 1 on iPhone
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, 1);
#else
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);
#endif
    GL_CHECK_ERROR;

    switch(status)
    {
    case GL_FRAMEBUFFER_COMPLETE_OES:
        // All is good
        break;
    case GL_FRAMEBUFFER_UNSUPPORTED_OES:
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, 
            "All framebuffer formats with this texture internal format unsupported",
            "GLESFrameBufferObject::initialise");
    default:
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, 
            "Framebuffer incomplete or other FBO status error",
            "GLESFrameBufferObject::initialise");
    }
    
}

void GLESFrameBufferObject::bind()
{
    /// Bind it to FBO
	const GLuint fb = mMultisampleFB ? mMultisampleFB : mFB;
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, fb);
    GL_CHECK_ERROR;
}

void GLESFrameBufferObject::swapBuffers()
{
#if GL_APPLE_framebuffer_multisample
	if (mMultisampleFB)
	{
		// blit from multisample buffer to final buffer, triggers resolve
		glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, mMultisampleFB);
        GL_CHECK_ERROR;
		glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, mFB);
        GL_CHECK_ERROR;
        glResolveMultisampleFramebufferAPPLE();
        GL_CHECK_ERROR;
//			glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
	}
#endif
}

void GLESFrameBufferObject::attachDepthBuffer( U2DepthBuffer *depthBuffer )
{
	U2GLESDepthBuffer *glDepthBuffer = static_cast<U2GLESDepthBuffer*>(depthBuffer);

	glBindFramebufferOES(GL_FRAMEBUFFER_OES, mMultisampleFB ? mMultisampleFB : mFB );

	if( glDepthBuffer )
	{
		GLESRenderBuffer *depthBuf   = glDepthBuffer->getDepthBuffer();
		GLESRenderBuffer *stencilBuf = glDepthBuffer->getStencilBuffer();

		//Attach depth buffer, if it has one.
		if( depthBuf )
			depthBuf->bindToFramebuffer( GL_DEPTH_ATTACHMENT_OES, 0 );

		//Attach stencil buffer, if it has one.
		if( stencilBuf )
			stencilBuf->bindToFramebuffer( GL_STENCIL_ATTACHMENT_OES, 0 );
	}
	else
	{
		glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES,
									  GL_RENDERBUFFER_OES, 0);
		glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES,
									  GL_RENDERBUFFER_OES, 0);
	}
}
//-----------------------------------------------------------------------------
void GLESFrameBufferObject::detachDepthBuffer()
{
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, mMultisampleFB ? mMultisampleFB : mFB );
	glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, 0 );
	glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES,
								  GL_RENDERBUFFER_OES, 0 );
}

size_t GLESFrameBufferObject::getWidth()
{
    assert(mColour[0].buffer);
    return mColour[0].buffer->getWidth();
}
size_t GLESFrameBufferObject::getHeight()
{
    assert(mColour[0].buffer);
    return mColour[0].buffer->getHeight();
}
PixelFormat GLESFrameBufferObject::getFormat()
{
    assert(mColour[0].buffer);
    return mColour[0].buffer->getFormat();
}
GLsizei GLESFrameBufferObject::getFSAA()
{
    return mNumSamples;
}
